package query.insert

import ast.expr.SqlIdentifierExpr
import ast.statement.insert.SqlInsert
import expr.DB
import expr.QueryTableColumn
import expr.TableSchema
import query.ReviseQuery
import util.toSqlString
import visitor.getExpr
import java.sql.Connection
import kotlin.reflect.KProperty
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.declaredMembers
import kotlin.reflect.jvm.javaField

class Insert(
    var db: DB = DB.MYSQL,
    override var conn: Connection? = null,
    override var isTransaction: Boolean = false
) : ReviseQuery() {
    constructor(db: DB) : this(db, null, false)

    private var sqlInsert = SqlInsert()

    private var columns = mutableListOf<String>()

    private var records = mutableListOf<Any>()

    private var incrKey = ""

    infix fun <T : TableSchema> into(table: T): Insert {
        sqlInsert.table = SqlIdentifierExpr(table.tableName)

        val clazz = table::class
        val declaredMemberProperties = clazz.declaredMemberProperties

        val properties = declaredMemberProperties.map { it.name to it.getter.call(table) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to it.second as QueryTableColumn }
            .filter {
                if (it.second.incr) {
                    incrKey = it.first
                }
                !it.second.incr
            }

        properties.forEach {
            columns.add(it.first)
            sqlInsert.columns.add(SqlIdentifierExpr(it.second.column))
        }

        return this
    }

    infix fun insert(entity: Any): Insert {
        val clazz = entity::class
        val companion = clazz.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        into(companion as TableSchema)

        value(entity)

        return this
    }

    infix fun insert(entity: List<Any>): Insert {
        if (entity.isNotEmpty()) {
            val clazz = entity[0]::class
            val companion = clazz.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
            into(companion as TableSchema)

            values(entity)
        }

        return this
    }

    infix fun value(obj: Any): Insert {
        val clazz = obj::class
        val properties = clazz.declaredMemberProperties.map { it.name to it.getter.call(obj) }.toMap()
        val value = columns.map { getExpr(properties[it]) }

        sqlInsert.values.add(value)

        records.add(obj)

        return this
    }

    infix fun <T : Any> values(objList: List<T>): Insert {
        objList.forEach {
            value(it)
        }

        return this
    }

    fun <T : Any> values(vararg obj: T): Insert {
        return values(obj.toList())
    }

    override fun sql() = toSqlString(sqlInsert, db)

    override fun exec(): Int {
        val result = database.execReturnKey(conn!!, this.sql())
        if (!isTransaction) {
            conn!!.close()
        }

        result.forEachIndexed { index, item ->
            val clazz = records[index]::class
            val field = (clazz.declaredMembers.find { it.name == incrKey } as KProperty).javaField
            field?.isAccessible = true
            val convert = convertIncrKey(item, field?.type?.typeName!!)
            field.set(records[index], convert)
            field.isAccessible = false
        }
        return this.sqlInsert.values.size
    }

    private fun convertIncrKey(key: Long, javaType: String): Any {
        return when (javaType) {
            "java.lang.Byte" -> key.toByte()
            "java.lang.Short" -> key.toShort()
            "java.lang.Integer" -> key.toInt()
            "java.lang.Long" -> key
            else -> key.toString()
        }
    }
}